//
// assembly.cs: Assembly declaration and specifications
//
// Authors:
//   Miguel de Icaza (miguel@ximian.com)
//   Marek Safar (marek.safar@gmail.com)
//
// Copyright 2001, 2002, 2003 Ximian, Inc.
// Copyright 2004-2011 Novell, Inc.
// Copyright 2011-2013 Xamarin Inc
//


using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using Mono.Security.Cryptography;
using Mono.CompilerServices.SymbolWriter;

#if STATIC
using IKVM.Reflection;
using IKVM.Reflection.Emit;
using SecurityType = System.Collections.Generic.List<IKVM.Reflection.Emit.CustomAttributeBuilder>;
#else
using SecurityType = System.Collections.Generic.Dictionary<System.Security.Permissions.SecurityAction, System.Security.PermissionSet>;
using System.Reflection;
using System.Reflection.Emit;
#endif

namespace Teal.Compiler
{
	public class AssemblyResource : IEquatable<AssemblyResource>
	{
		public AssemblyResource (string fileName, string name)
			: this (fileName, name, false)
		{
		}

		public AssemblyResource (string fileName, string name, bool isPrivate)
		{
			FileName = fileName;
			Name = name;
			Attributes = isPrivate ? ResourceAttributes.Private : ResourceAttributes.Public;
		}

		public ResourceAttributes Attributes { get; private set; }
		public string Name { get; private set; }
		public string FileName { get; private set; }
		public bool IsEmbeded { get; set; }

		#region IEquatable<AssemblyResource> Members

		public bool Equals (AssemblyResource other)
		{
			return Name == other.Name;
		}

		#endregion
	}

	//
	// A placeholder class for assembly attributes when emitting module
	//
	class AssemblyAttributesPlaceholder : CompilerGeneratedContainer
	{
		static readonly string TypeNamePrefix = "<$AssemblyAttributes${0}>";
		public static readonly string AssemblyFieldName = "attributes";

	    public AssemblyAttributesPlaceholder (ModuleDefinition parent, string outputName)
			: base (parent, new MemberName (GetGeneratedName (outputName)), Modifiers.@static)
		{
            //assembly = new FieldDefinition (this, new TypeExpression ( Compiler.BuiltinTypes.Object, startLocation2), Modifiers.@public | Modifiers.@static,
            //    new MemberName (AssemblyFieldName), null);

            //AddField (assembly);
		}

		public void AddAssemblyAttribute (MethodReference ctor, byte[] data)
		{
			//assembly.SetCustomAttribute (ctor, data);
		}

		public static string GetGeneratedName (string outputName)
		{
			return string.Format (TypeNamePrefix, outputName);
		}
	}

	//
	// Extension to System.Reflection.Emit.AssemblyBuilder to have fully compatible
	// compiler. This is a default implementation for framework System.Reflection.Emit
	// which does not implement any of the methods
	//
	public class AssemblyBuilderExtension
	{
         
		public virtual System.Reflection.Module AddModule (string module)
		{
            Compiler.Report.RuntimeMissingSupport(Location2.Null, "-addmodule");
			return null;
		}

		public virtual void AddPermissionRequests (PermissionSet[] permissions)
		{
            Compiler.Report.RuntimeMissingSupport(Location2.Null, "assembly declarative security");
		}

		public virtual void AddTypeForwarder (TypeReference type, Location2 loc)
		{
			Compiler.Report.RuntimeMissingSupport (loc, "TypeForwardedToAttribute");
		}

		public virtual void DefineWin32IconResource (string fileName)
		{
			Compiler.Report.RuntimeMissingSupport (Location2.Null, "-win32icon");
		}

		public virtual void SetAlgorithmId (uint value, Location2 loc)
		{
			Compiler.Report.RuntimeMissingSupport (loc, "AssemblyAlgorithmIdAttribute");
		}

		public virtual void SetCulture (string culture, Location2 loc)
		{
			Compiler.Report.RuntimeMissingSupport (loc, "AssemblyCultureAttribute");
		}

		public virtual void SetFlags (uint flags, Location2 loc)
		{
			Compiler.Report.RuntimeMissingSupport (loc, "AssemblyFlagsAttribute");
		}

		public virtual void SetVersion (Version version, Location2 loc)
		{
			Compiler.Report.RuntimeMissingSupport (loc, "AssemblyVersionAttribute");
		}
	}

	abstract class AssemblyReferencesLoader
	{ 

		protected readonly List<string> paths;
	   // protected ReflectionImporter importer;

	    protected AssemblyReferencesLoader()
		{ 
			paths = new List<string> ();
			paths.Add (Directory.GetCurrentDirectory ());
			paths.AddRange (Compiler.options.ReferencesLookupPaths);
		}

        //public ReflectionImporter Importer {
        //    get {
        //        return importer;
        //    }
        //}

	    protected void Error_FileNotFound (string fileName)
		{
			Compiler.Report.Error (6, "Metadata file `{0}' could not be found", fileName);
		}

		protected void Error_FileCorrupted (string fileName)
		{
			Compiler.Report.Error (9, "Metadata file `{0}' does not contain valid metadata", fileName);
		}

		protected void Error_AssemblyIsModule (string fileName)
		{
			Compiler.Report.Error (1509,
				"Referenced assembly file `{0}' is a module. Consider using `-addmodule' option to add the module",
				fileName);
		}

		protected void Error_ModuleIsAssembly (string fileName)
		{
			Compiler.Report.Error (1542,
				"Added module file `{0}' is an assembly. Consider using `-r' option to reference the file",
				fileName);
		}

        protected void LoadReferencesCore(ModuleDefinition module, out Assembly corlib_assembly, out List<Tuple<object, Assembly>> loaded)
		{


            loaded = new List<Tuple<object, Assembly>>();

			//
			// Load mscorlib.dll as the first
			//
			if (Compiler.options.StdLib) {
				corlib_assembly = LoadAssemblyFile ("mscorlib.dll", true);
			} else {
                corlib_assembly = default(Assembly);
			}

            Assembly a;
			foreach (var r in Compiler.options.importedLibs) {
                if (r.alias != null) {
                    continue;
                }
				a = LoadAssemblyFile (r.path, false);
                if (a == null || EqualityComparer<Assembly>.Default.Equals(a, corlib_assembly))
					continue;

                //var key = Tuple.Create (module.GlobalRootNamespace, a);
                //if (loaded.Contains (key))
                //    continue;

                //loaded.Add (key);
			}

			if (corlib_assembly == null) {
				//
				// Requires second pass because HasObjectType can trigger assembly load event
				//
				for (int i = 0; i < loaded.Count; ++i) {
					var assembly = loaded [i];

					//
					// corlib assembly is the first referenced assembly which contains System.Object
					//
					if (HasObjectType (assembly.Item2)) {
						corlib_assembly = assembly.Item2;
						loaded.RemoveAt (i);
						break;
					}
				}
			}

            foreach (var r in Compiler.options.importedLibs) {
                if (r.alias == null) {
                    continue;
                }
				a = LoadAssemblyFile (r.path, false);
				if (a == null)
					continue;

				//var key = Tuple.Create (module.CreateRootNamespace (r.alias), a);
                //if (loaded.Contains (key))
                //    continue;

                //loaded.Add (key);
			}

			if (Compiler.options.LoadDefaultReferences) {
				foreach (string r in GetDefaultReferences ()) {
					a = LoadAssemblyFile (r, true);
					if (a == null)
						continue;

                    //var key = Tuple.Create (module.GlobalRootNamespace, a);
                    //if (loaded.Contains (key))
                    //    continue;

                    //loaded.Add (key);
				}
			}

		}

	    protected virtual string[] GetDefaultReferences ()
	    {
	        //
	        // For now the "default config" is harcoded into the compiler
	        // we can move this outside later
	        //
	        var default_references = new List<string> (8);

	        default_references.Add ("System");
	        default_references.Add ("System.Xml");
#if NET_2_1
			default_references.Add ("System.Net");
			default_references.Add ("System.Windows");
			default_references.Add ("System.Windows.Browser");
#endif

	        if (Compiler.options.languageVersion > LanguageVersion.ISO_2)
	            default_references.Add ("System.Core");
	        if (Compiler.options.languageVersion > LanguageVersion.V_3)
	            default_references.Add ("Microsoft.CSharp");

	        return default_references.ToArray ();
	    }

	    protected static string GetSystemDir ()
	    {
	        return Path.GetDirectoryName (typeof (object).Assembly.Location);
	    }

	    public virtual bool HasObjectType (Assembly assembly)
	    {
	        return assembly.GetType (Compiler.BuiltinTypes.Object.FullName) != null;
	    }

	    public Assembly LoadAssemblyFile (string assembly, bool isImplicitReference)
	    {
	        Assembly a = null;

	        try {
	            try {
	                char[] path_chars = { '/', '\\' };

	                if (assembly.IndexOfAny (path_chars) != -1) {
	                    a = Assembly.LoadFrom (assembly);
	                } else {
	                    string ass = assembly;
	                    if (ass.EndsWith (".dll") || ass.EndsWith (".exe"))
	                        ass = assembly.Substring (0, assembly.Length - 4);
	                    a = Assembly.Load (ass);
	                }
	            } catch (FileNotFoundException) {
	                bool err = !isImplicitReference;
	                foreach (string dir in paths) {
	                    string full_path = Path.Combine (dir, assembly);
	                    if (!assembly.EndsWith (".dll") && !assembly.EndsWith (".exe"))
	                        full_path += ".dll";

	                    try {
	                        a = Assembly.LoadFrom (full_path);
	                        err = false;
	                        break;
	                    } catch (FileNotFoundException) {
	                    }
	                }

	                if (err) {
	                    Error_FileNotFound (assembly);
	                    return a;
	                }
	            }
	        } catch (BadImageFormatException) {
	            Error_FileCorrupted (assembly);
	        }

	        return a;
	    }

	}
}
